#include <iostream>

#include <math.h>

#include <GL/gl.h>
#include "SDL.h"

#include "asserts.hpp"
#include "battle.hpp"
#include "draw_scenario.hpp"
#include "draw_utils.hpp"
#include "foreach.hpp"
#include "frame_rate_handler.hpp"
#include "gamemap.hpp"
#include "hex_geometry.hpp"
#include "human_player.hpp"
#include "key.hpp"
#include "pathfind.hpp"
#include "raster.hpp"
#include "scenario.hpp"
#include "tile.hpp"
#include "tooltip.hpp"
#include "unit_utils.hpp"
#include "wm.hpp"

namespace game {

namespace {
const int ScrollSpeed = 10;
const human_player* current_perspective_;
}

const human_player* human_player::current_perspective()
{
	return current_perspective_;
}

human_player::human_player(wml::const_node_ptr node) : player(node), panel_(*this)
{}

human_player::~human_player()
{
	if(current_perspective_ == this) {
		current_perspective_ = NULL;
	}
}

void human_player::play_turn(scenario& scenario)
{
	current_perspective_ = this;
	CKey key;
	frame_rate_handler fps;

	if(commanders().empty() == false) {
		set_scroll_target(commanders().front()->loc());
	}

	while(!scenario.finished()) {
		next_draw_cycle();

		int mousex, mousey;
		SDL_GetMouseState(&mousex, &mousey);
		const int selectx = scroll_x() + mousex;
		const int selecty = scroll_y() + mousey;

		const hex::location new_mouseover = pixel_pos_to_loc(selectx, selecty);
		if(new_mouseover != mouseover_) {
			mouseover_ = new_mouseover;
			panel_.set_loc(mouseover_);
			unit_ptr target = scenario.get_unit(mouseover_);
			panel_.set_unit2(target);

			if(attacks_.count(mouseover_)) {
				ASSERT_LOG(target, "Could not find target unit");
				battle_ = battle_ptr(new battle(selection_, target));
			} else {
				battle_ = battle_ptr();
			}
		}

		if(selection_) {
			selection_->set_selected();
		}

		SDL_Event event;
		while(SDL_PollEvent(&event)) {
			const bool swallowed = panel_.process_event(event, false);
			if(swallowed) {
				continue;
			}

			switch(event.type) {
			case SDL_QUIT:
				throw game_aborted();
			case SDL_KEYDOWN:
				switch(event.key.keysym.sym) {
				case SDLK_ESCAPE:
					throw game_aborted();
				case SDLK_SPACE:
					return;
				}
				break;
			case SDL_MOUSEBUTTONDOWN: {
				if(event.button.button == SDL_BUTTON_LEFT) {
					if(battle_) {
						if(selection_ == free_attack_ || selection_->get_commander().use_command()) {
							process_battle(scenario);
							free_attack_ = const_unit_ptr();
						} else {
							//TODO: report running out of commands.
						}
					} else if(routes_.count(mouseover_) && !scenario.get_unit(mouseover_)) {
						if(selection_->get_commander().use_command()) {
							//moving to a chosen location.
							selection_->use_moves(routes_[mouseover_].cost);
							selection_->move_along_path(routes_[mouseover_].steps);
							scenario.move_unit(selection_, routes_[mouseover_].steps.back());

							//make it so this unit's next attack will be free.
							free_attack_ = selection_;

							//find nearby enemies who we can attack.
							routes_.clear();

							find_attacks(scenario);

							if(attacks_.empty()) {
								selection_ = unit_ptr();
								panel_.set_unit(selection_);
							}
						} else {
							//TODO: report running out of commands.
						}
					} else {
						unit_ptr unit = scenario.get_unit(mouseover_);
						if(unit && this == &unit->get_commander().owner()) {
							selection_ = unit;
							panel_.set_unit(selection_);
		
							routes_.clear();
							hex::find_possible_moves(unit->loc(), unit->moves_left(), *unit, routes_);
							find_attacks(scenario);
						}
					}
				} else if(event.button.button == SDL_BUTTON_RIGHT) {
					selection_ = unit_ptr();
					panel_.set_unit(selection_);
					routes_.clear();
					attacks_.clear();
				}
				break;
			}
			}
		}

		if(key[SDL_SCANCODE_UP]) {
			scroll_to(scroll_x(), scroll_y()-ScrollSpeed);
		}

		if(key[SDL_SCANCODE_DOWN]) {
			scroll_to(scroll_x(), scroll_y()+ScrollSpeed);
		}

		if(key[SDL_SCANCODE_LEFT]) {
			scroll_to(scroll_x()-ScrollSpeed, scroll_y());
		}

		if(key[SDL_SCANCODE_RIGHT]) {
			scroll_to(scroll_x()+ScrollSpeed, scroll_y());
		}

		draw(scenario);
		wm::swap_buffers();
		fps.next_frame();
	}
}

void human_player::recalculate_routes()
{
	routes_.clear();
	if(selection_) {
		hex::find_possible_moves(selection_->loc(), selection_->moves_left(), *selection_, routes_);
		find_attacks(*scenario::current());
	}
}

void human_player::draw(const scenario& scenario) const
{
	graphics::texture mask = graphics::texture::get("/terrain/mask.png");

	graphics::prepare_raster();

	glPushMatrix();
	glTranslatef(-scroll_x(), -scroll_y(), 0.0);

	const hex::gamemap& map = scenario.map();

	for(int x = 0; x != map.width(); ++x) {
		for(int y = 0; y != map.height(); ++y) {
			hex::location loc(x,y);
			hex::const_tile_ptr tile = map.get_tile(loc);
			ASSERT_LOG(tile, "Could not find tile at " << loc.x() << "," << loc.y());
			GLfloat r = 1.0, g = 1.0, b = 1.0, a = 1.0;
			if(routes_.empty() == false && routes_.count(loc) == 0) {
				r *= 0.5;
				g *= 0.5;
				b *= 0.5;
			}

			glColor4f(r, g, b, a);

			tile->draw();
		}
	}

	for(int x = 0; x != map.width(); ++x) {
		for(int y = 0; y != map.height(); ++y) {
			hex::location loc(x,y);
			hex::const_tile_ptr tile = map.get_tile(loc);
			ASSERT_LOG(tile, "Could not find tile at " << loc.x() << "," << loc.y());
			GLfloat r = 1.0, g = 1.0, b = 1.0, a = 1.0;
			if(routes_.empty() == false && routes_.count(loc) == 0) {
				r *= 0.5;
				g *= 0.5;
				b *= 0.5;
			}

			glColor4f(r, g, b, a);

			tile->draw_overlay();
		}
	}

	const commander_unit* commander_selected = NULL;
	if(selection_) {
		commander_selected = &selection_->get_commander();
	} else {
		const_unit_ptr u = scenario.get_unit(mouseover_);
		if(u) {
			commander_selected = &u->get_commander();
		}
	}

	for(int x = 0; x != map.width(); ++x) {
		for(int y = 0; y != map.height(); ++y) {
			hex::location loc(x,y);
			hex::const_tile_ptr tile = map.get_tile(loc);
			ASSERT_LOG(tile, "Could not find tile at " << loc.x() << "," << loc.y());
			//draw the commander's command radius
			if(commander_selected && hex::distance_between(commander_selected->loc(), loc) <= commander_selected->command_radius()) {
				glBlendFunc(GL_SRC_ALPHA, GL_ONE);
				glColor4f(sin(draw_cycle()/10.0)*0.5, 0, cos(draw_cycle()/10.0)*0.5, 0.5);
				const int x = tile_pixel_x(loc) + HexWidth/2;
				const int y = tile_pixel_y(loc) + HexHeight/2;
				graphics::blit_texture(mask, x - mask.width()/2, y - mask.height()/2);
				glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			}

			if(loc == mouseover_) {
				glBlendFunc(GL_SRC_ALPHA, GL_ONE);
				glColor4f(1.0, 1.0, 1.0, 0.2);
				const int x = tile_pixel_x(loc) + HexWidth/2;
				const int y = tile_pixel_y(loc) + HexHeight/2;
				graphics::blit_texture(mask, x - mask.width()/2, y - mask.height()/2);
				glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			}

			if(attacks_.count(loc)) {
				glBlendFunc(GL_SRC_ALPHA, GL_ONE);
				glColor4f(1.0, 0.5 + sin(draw_cycle()/10.0)*0.5, sin(draw_cycle()/10.0)*0.5, 0.5);
				const int x = tile_pixel_x(loc) + HexWidth/2;
				const int y = tile_pixel_y(loc) + HexHeight/2;
				graphics::blit_texture(mask, x - mask.width()/2, y - mask.height()/2);
				glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			}
		}
	}

	glColor4f(1.0, 1.0, 1.0, 1.0);

	if(routes_.count(mouseover_)) {
		draw_arrow(routes_.find(mouseover_)->second.steps);
	}

	for(scenario::UnitMap::const_iterator i = scenario.unit_map().begin();
	    i != scenario.unit_map().end(); ++i) {
		if(i->second->is_moving()) {
			draw_arrow(i->second->current_move_path());
		}
	}

	for(scenario::UnitMap::const_iterator i = scenario.unit_map().begin();
	    i != scenario.unit_map().end(); ++i) {
		i->second->draw();
	}

	glPopMatrix();

	panel_.draw();

	gui::draw_tooltip();

	if(battle_) {
		battle_->draw();
	}
}

void human_player::process_battle(scenario& scenario)
{
	frame_rate_handler fps;
	int frames_at_end = 10;
	while(!battle_->done() || frames_at_end) {
		if(battle_->done()) {
			--frames_at_end;
		}

		battle_->process();
		draw(scenario);
		wm::swap_buffers();

		fps.next_frame();
	}


	selection_ = unit_ptr();
	panel_.set_unit(selection_);
	scenario.remove_dead_units();
	battle_ = battle_ptr();
	attacks_.clear();
	routes_.clear();
}

void human_player::find_attacks(scenario& scenario)
{
	attacks_.clear();
	std::vector<hex::location> in_range;
	hex::get_tiles_in_radius(selection_->loc(), selection_->attack_range(), in_range);
	foreach(const hex::location& loc, in_range) {
		unit_ptr target = scenario.get_unit(loc);
		if(target && game::units_hostile(*target, *selection_)) {
			attacks_.insert(loc);
		}
	}
}

void human_player::start_scenario()
{
	player::start_scenario();
	current_perspective_ = this;
}

}
